#include "pixelfontblitter.h"
#include "graphics.h"

/** key mask value unused by SDLMod, differentiates between key release */
#define DV_SDLConsoleKeyEvent_KEY_UP_MOD	0x0004

/** long (int) sized data structure to keep track of key presses */
struct DV_SDLConsoleKeyEvent
{
	short mod, value;
	int toInt()
	{
		return value | (mod << 16);
	}
	void loadFromInt(const int & i)
	{
		value = i & 0xffff;
		mod = i >> 16;
	}
	void loadFromSDLEvent(const SDL_Event & e)
	{
		mod = e.key.keysym.mod;
		value = e.key.keysym.sym;
		switch(e.type)
		{
		case SDL_KEYUP:
			mod |= DV_SDLConsoleKeyEvent_KEY_UP_MOD;
			break;
		}
	}
	bool isKeyUp()
	{
		return (mod & DV_SDLConsoleKeyEvent_KEY_UP_MOD) != 0;
	}
};


/** @return true if the console wants to keep running */
bool PixelFontArea::running(){return !m_userWantsToQuit;}

/** accessor */
int PixelFontArea::getCursorIndex(){return m_index;}

/** the amount of chars used in the console buffer */
int PixelFontArea::getLength(){return m_length;}

/** mutator */
bool PixelFontArea::setAt(int a_index, const int & a_value)
{
	if(a_index >= 0 && a_index < m_buffer.size())
	{
		m_buffer.set(a_index, a_value);
		return true;
	}
	else
	{
		printf("bad index %d beind set to \'%c\' in buffer size %d", a_index, a_value, m_buffer.size());
		return false;
	}
}

/** mutator */
bool PixelFontArea::setAt(const int & a_x, const int & a_y, const int & a_value)
{
	int i = getIndexAt(SPoint(a_x, a_y));
	if(i >= 0)
	{
		return setAt(i, a_value);
	}
	return false;
}

/** 
 * @param a_start where in the console buffer to start reading from
 * @param a_length how much to read from the console buffer
 * @param a_bufferToCopyInto where to copy the data read from the console buffer
 */
void PixelFontArea::copyBufferInto(const int & a_start, const int & a_length, char * a_bufferToCopyInto)
{
	for(int i = 0; i < a_length; ++i)
	{
		a_bufferToCopyInto[i] = m_buffer.get(i+a_start);
	}
}

/**
 * converts a base 10 number that was written in ASCII into an integer
 * @param a_start where in the console buffer the base 10 number begins
 * @param a_len how many letters the base 10 number has
 */
int PixelFontArea::bufferSectionToNumber(const int & a_start, const int & a_len)
{
	char letter;
	int place = 1;
	int value = 0;
	int digit;
	for(int i = a_len-1; i >= 0; --i)
	{
		letter = getAt(a_start+i);
		if(letter < '0' || letter > '9')
		{
			// cannot convert...
			return -1;
		}
		digit = letter -= '0';
		value += digit * place;
		place *= 10;
	}
	return value;
}

bool PixelFontArea::shouldBeCapitalized()
{
    // Check for the presence of each SDLMod value
    // This looks messy, but there really isn't
    // a clearer way.
/*
    if( mod & KMOD_NUM ) printf( "NUMLOCK " );
    if( mod & KMOD_CAPS ) printf( "CAPSLOCK " );
    if( mod & KMOD_LCTRL ) printf( "LCTRL " );
    if( mod & KMOD_RCTRL ) printf( "RCTRL " );
    if( mod & KMOD_RALT ) printf( "RALT " );
    if( mod & KMOD_LALT ) printf( "LALT " );
    if( mod & KMOD_CTRL ) printf( "CTRL " );
    if( mod & KMOD_SHIFT ) printf( "SHIFT " );
    if( mod & KMOD_ALT ) printf( "ALT " );
*/
	//bool shiftDown = ( m_keyboardMod & KMOD_SHIFT ) != 0;
	//bool capsOn = ( m_keyboardMod & KMOD_CAPS ) != 0;
	return (( m_keyboardMod & KMOD_SHIFT ) ^ ( m_keyboardMod & KMOD_CAPS )) != 0;//(shiftDown && !capsOn) || (capsOn && !shiftDown);
}

/**
 * @param a_index the letter in the text buffer that you want the location of
 * @param a_out_cursor col/row coordinate of the index
 * @param a_out_pixelCursor x/y + w/h of the letter
 */
void PixelFontArea::locOf(const int & a_index,
						  SPoint & a_out_cursor,
						  SRect & a_out_pixelCursor)
{
	a_out_pixelCursor.setPosition(m_area.getPosition().difference(m_drawOffset));
	switch(m_consoleMode)
	{
	case Tile:
		a_out_cursor.setX(a_index % m_consoleSize.x);
		a_out_cursor.setY(a_index / m_consoleSize.x);
		break;
	case Stream:
		for(int i = 0; i < a_index; ++i)
			advanceCursor(a_out_cursor, a_out_pixelCursor, getAt(i));
		break;
	}
}

/** sets the length of the text buffer */
void PixelFontArea::setLength(const int & a_length)
{
	m_length = a_length;
	if(m_length > m_buffer.size())
	{
		m_length = m_buffer.size();
	}
	else if(m_length < 0)
	{
		m_length = 0;
	}
}

/** advances the given index along the buffer */
bool PixelFontArea::advanceCursorIndex(const int & a_d)
{
	return setCursorIndex(getCursorIndex() + a_d);
}

/** starts the cursor flashing */
void PixelFontArea::startCursorFlash()
{
	m_cursorFlashTimer = 0;
	m_currentCursorIcon = m_cursorIcon;
}

/** calculates how many spaces a tab would move at the given x position */
int PixelFontArea::spacesToTabFrom(const int a_x)
{
	return m_spacesPerTab - (a_x % m_spacesPerTab);
}

/** calculates how many spaces a tab would move at the given x position */
int PixelFontArea::spacesToTabFromPixel(const int a_x)
{
	int w = m_font->getImageSourceOf(' ').getWidth();
	return m_spacesPerTab*w - (a_x % (m_spacesPerTab*w));
}

/** write the char buffer into the console buffer at the given index */
void PixelFontArea::write(const char * a_buffer, const int & a_size)
{
	int size = a_size;
	int index = getCursorIndex();
	for(int i = 0; i < size; ++i)
	{
		setAt(index+i, a_buffer[i]);
	}
	if(index+a_size > getLength())
	{
		setLength(getLength()+a_size);
	}
	advanceCursorIndex(size);
}

void PixelFontArea::write(const char * a_buffer)
{
	write(a_buffer, strlen(a_buffer));
}

/** write the given char into the console buffer at the given index */
void PixelFontArea::write(const int & a_letter)
{
	write((char*)&a_letter, 1);
}

/** accessor */
int PixelFontArea::getAt(int a_index)
{
	return m_buffer.get(a_index);
}

/** accessor */
int PixelFontArea::getAt(const int & a_x, const int & a_y)
{
	int i = getIndexAt(SPoint(a_x, a_y));
	return (i >= 0)?getAt(i):-1;
}

/** return true if the cursor was set without trouble */
bool PixelFontArea::setCursorIndex(int a_index)
{
	bool forcedInBounds = false;
	switch(m_consoleMode)
	{
	case Tile:
		if(a_index >= getLength())
		{	a_index -= getLength();	forcedInBounds = true;	}
		else if(a_index < 0)
		{	a_index += getLength();	forcedInBounds = true;	}
		break;
	case Stream:
		if(a_index > getLength())
		{
			a_index = getLength();
			forcedInBounds = true;
		}
		else if(a_index < 0)
		{	a_index = 0;			forcedInBounds = true;	}
		break;
	}
	// recalculate x/y coordinates of the cursor
	if(m_index > a_index)
	{
		m_index = 0;
		m_cursorLoc.x = 0;
		m_cursorLoc.y = 0;
		m_cursor.x = m_area.x-m_drawOffset.x;
		m_cursor.y = m_area.y-m_drawOffset.y;
	}
	for(int i = m_index; i < a_index; ++i)
	{
		advanceCursor(m_cursorLoc, m_cursor, getAt(i));
	}
	// scroll Y axis
	int dist = (m_cursor.y+m_cursor.h)-(m_area.y+m_area.h);
	if(dist > 0)
	{
		m_drawOffset.y += dist;
		m_cursor.y -= dist;
	}
	else if(m_cursor.y < m_area.y)
	{
		dist = m_area.y-m_cursor.y;
		m_drawOffset.y -= dist;
		m_cursor.y += dist;
	}
	// scroll X axis
	dist = (m_cursor.x+m_cursor.w)-(m_area.x+m_area.w);
	if(dist > 0)
	{
		m_drawOffset.x += dist;
		m_cursor.x -= dist;
	}
	else if(m_cursor.x < m_area.x)
	{
		dist = m_area.x-m_cursor.x;
		m_drawOffset.x -= dist;
		m_cursor.x += dist;
	}
	m_index = a_index;
	return !forcedInBounds;
}

/** logic for moving using horizontal and vertical controls */
bool PixelFontArea::moveCursor(const int & a_dx, const int & a_dy)
{
	switch(m_consoleMode)
	{
	case Tile:
		return advanceCursorIndex(a_dx + a_dy*m_consoleSize.x);
	case Stream:
		return setCursorIndex(getIndexAt(SPoint(m_cursorLoc.x+a_dx, m_cursorLoc.y+a_dy)));
	}
	return false;
}

void PixelFontArea::clear()
{
	m_buffer.setAll(0);
	setCursorIndex(0);
}

PixelFontArea::PixelFontArea(
	PixelFont * a_font, 
	const SRect & a_areaInPixels, 
	const int & a_rows, 
	const int & a_cols, 
//	const SRect & a_charSizeInPixels, 
	const DV_SDLConsoleMode & a_mode,
	SDL_Surface * a_defaultScreen)
{
	m_screen = a_defaultScreen;
	m_userWantsToQuit = false;
	m_consoleMode = a_mode;//Stream;//Tile;
	//m_ascii = a_tileSource;//load_image(a_asciiBMP);
	m_font = a_font;
	//setTransparentColor(m_ascii, 0);
//	m_cursor = a_charSizeInPixels;
	m_drawOffset.set(0,0);
	m_cursor.x = a_areaInPixels.x;
	m_cursor.y = a_areaInPixels.y;
	m_area = a_areaInPixels;
	m_consoleSize.x = a_cols;//m_area.w / m_cursor.w;
	m_consoleSize.y = a_rows;//m_area.h / m_cursor.h;
	int bufferAllocation = m_consoleSize.x * m_consoleSize.y;
	m_buffer.allocateToSize(bufferAllocation);
	switch(m_consoleMode)
	{
	case Tile:
		m_length = bufferAllocation;
		break;
	case Stream:
	default:
		m_length = 0;
		break;
	}
	m_index = 0;
	clear();
	m_spacesPerTab = 8;
	m_cursorFlashDuration = 500;
	m_cursorIcon = 22;
	startCursorFlash();
}

PixelFontArea::~PixelFontArea()
{
	m_buffer.release();
	// the SDL_Surface is given by the caller... do not free it yourself.
	//SDL_FreeSurface( m_ascii );
	//m_ascii = 0;
	m_font = 0;
}

/** used to help debug the console */
void progressBar(SDL_Surface * a_destination, SRect * a_area, int a_start, int a_end, int bgcolor, int fgcolor)
{
	SRect fore = *a_area;
	fore.x += a_start;
	fore.w -= (a_area->w-a_end)+a_start;
	if(bgcolor != -1)
	SDL_FillRect(a_destination, a_area, bgcolor);
	if(fgcolor != -1)
	SDL_FillRect(a_destination, &fore, fgcolor);
}

void PixelFontArea::draw()
{
	if(m_screen)
	{
		draw(m_screen);
		if( SDL_Flip( m_screen ) == -1 )
			m_userWantsToQuit = true;
	}
}

SRect PixelFontArea::advanceCursor(SPoint & p, SRect & cursor, const int & a_letter)
{
	bool printableChar;
	p.addX(1);
	// get the dimensions of the given character (may depend on the current location in the area)
	SPoint charDimensions = getCharSize(a_letter, cursor, printableChar);
	// set the size of the cursor based on the letter we're printing
	cursor.setDimension(charDimensions);
	// keep track of where the old cursor was (check collision by asking for forgiveness)
	SRect writeableArea(cursor);
	// move the cursor
	cursor.set(cursor.unitsOver(SPoint(1,0)));
	// after a character is printed, we don't know what size the next chat will be.
	cursor.setWidth(0);
	bool goToNextLine = cursor.getMaxX() >= m_area.getMaxX();
	bool printCharOnNextLine = cursor.getMaxX() > m_area.getMaxX();
	// if the cursor should move to the next line
	if(goToNextLine && (!printableChar || p.getX() != 1))
	{
		// go to the next line
		cursor.set(writeableArea);
		cursor.setX(m_area.getX());
		cursor.set(cursor.unitsOver(SPoint(0,1)));
		// if the char should be printed on the next line
		if(printCharOnNextLine)
		{
			writeableArea.set(cursor);
			// move the cursor past the character
			cursor.set(writeableArea.unitsOver(SPoint(1,0)));
		}
		cursor.setWidth(0);
		p.setX(0);
		p.addY(1);
	}
	return writeableArea;
}

/** 
 * @param a_point an X/Y coordinate to determine the letter index at
 * @return the 1D index nearest to the given X/Y
 */
int PixelFontArea::getIndexAt(const SPoint & a_point)
{
	SRect cursor;
	cursor.setPosition(m_area.getPosition());
	SPoint p;
	int index = -1;
	for(int i = 0; i < getLength() && index < 0; ++i)
	{
		SRect letterArea = advanceCursor(p, cursor, getAt(i));
		if(letterArea.contains(a_point))
		{
			index = i;
		}
	}
	return index;
}
SPoint PixelFontArea::getCharSize(const char & a_letter, SRect & a_out_pixelCursor, bool & a_out_printable)
{
	a_out_printable = false;
	SPoint charSize;
	switch(a_letter)
	{
	// non-printing ascii characters
	case '\0':	case '\a':	case '\b':	case '\r':	break;
	// until the end of the line
	case '\n':	charSize.set( m_area.getMaxX() - a_out_pixelCursor.getX(), m_font->getHeight() );	break;
	case '\t':	charSize.set(spacesToTabFromPixel(a_out_pixelCursor.getX()), m_font->getHeight());	break;
	default:	charSize = m_font->getImageSourceOf(a_letter).getDimension();	
a_out_printable = m_font->isPrintable(a_letter);
	}
	return charSize;
}

void PixelFontArea::drawAtCursor(SDL_Surface * a_screen, const char & a_letter, 
				  SPoint & a_out_cursor, SRect & a_out_pixelCursor)
{
	SRect writeableArea = advanceCursor(a_out_cursor, a_out_pixelCursor, a_letter);
	if(m_font->isPrintable(a_letter))
	{
		m_font->drawGlyph(a_screen, a_letter, writeableArea.getPosition());
	}
}

void PixelFontArea::draw(SDL_Surface * a_destination)
{
	if(!a_destination)	return;
	// draw a bounds rectangle
	SDL_FillRect(a_destination, &m_area, 0x888888);
	VSDL_drawRect(a_destination, &m_area, 0xffffff, 1);
	// move the cursor back to the top of the console
	SRect cursor;
	cursor.setPosition(m_area.getPosition());
	SPoint p;
	SRect oldClip;
	SDL_GetClipRect(a_destination, &oldClip);
	SDL_SetClipRect(a_destination, &m_area);
	for(int i = 0; i < getLength(); ++i)
	{
		drawAtCursor(a_destination, getAt(i), p, cursor);
	}
	//if(m_currentCursorIcon)
	//{
		VSDL_drawRect(a_destination, &cursor, 0xffffff, 1);
	//	//VSDL_drawTile(m_ascii, &m_cursor, m_currentCursorIcon, a_destination, m_cursor.x, m_cursor.y);
	//	m_font->drawGlyph(a_destination, m_currentCursorIcon, cursor.getPosition());
	//	//drawRect(a_destination, &m_cursor, 0x00ff00, 1);
	//}
	SDL_SetClipRect(a_destination, &oldClip);
}

void PixelFontArea::update(int a_ms)
{
	while(handleQueuedEvents());
	m_cursorFlashTimer += a_ms;
	if(m_cursorFlashTimer > m_cursorFlashDuration)
	{
		m_cursorFlashTimer -= m_cursorFlashDuration;
		if(m_currentCursorIcon == 0)
		{
			m_currentCursorIcon = m_cursorIcon;
		}
		else
		{
			m_currentCursorIcon = 0;
		}
	}
}

bool PixelFontArea::handleSDL(const SDL_Event & e)
{
	bool eventQueued = false;
	switch(e.type)
	{
	case SDL_QUIT:
		m_userWantsToQuit = true;
		break;
	case SDL_KEYUP:
	case SDL_KEYDOWN:{
			DV_SDLConsoleKeyEvent consoleEvent;
			consoleEvent.loadFromSDLEvent(e);
			m_inputQueue.add(consoleEvent.toInt());
			eventQueued = true;
		break;}
	case SDL_MOUSEBUTTONDOWN:{
		SPoint press(e.button.x, e.button.y);
		int index = getIndexAt(press);
		printf("mouse press x%d y%d index%d\n", e.button.x, e.button.y, index);
		if(index >= 0)
		{
			m_index = index;
			printf("   letter: %c\n", getAt(index));
		}
		break;}
	}
	return eventQueued;
}

bool PixelFontArea::handleQueuedEvents()
{
	if(m_inputQueue.size() <= 0)
	{
		return false;
	}
	DV_SDLConsoleKeyEvent consoleEvent;
	consoleEvent.loadFromInt(m_inputQueue.pop());
	bool handled = true;
	m_keyboardMod = (SDLMod)consoleEvent.mod;
	int value = consoleEvent.value;
	// don't type when a key is released
	if(!consoleEvent.isKeyUp())
	{
		switch(value)
		{
		case SDLK_UP:	moveCursor(0, -1);		break; 
		case SDLK_DOWN: moveCursor(0, 1);		break; 
		case SDLK_LEFT: advanceCursorIndex(-1);	break;
		case SDLK_RIGHT:advanceCursorIndex(1);	break;
		case SDLK_RSHIFT: 
		case SDLK_LSHIFT:
		case SDLK_CAPSLOCK:
			break;
		case SDLK_BACKSPACE:
			if(advanceCursorIndex(-1))
			{
				setAt(getCursorIndex(), '\0');
			}
			break;
		case SDLK_RETURN:
			switch(m_consoleMode)
			{
			case Tile:
				write(10);
				moveCursor(-(getCursorIndex() % m_consoleSize.x), 1);
				break;
			case Stream:
				write(10);
				break;
			}
			break;
		default:
			if(value >= 0 && value <= 0xff)
			{
				if(shouldBeCapitalized())
				{
					if(value >= 'a' && value <= 'z')
					{
						value += 'A'-'a';
					}
					else switch(value)
					{
					case '1':	value = '!';	break;
					case '2':	value = '@';	break;
					case '3':	value = '#';	break;
					case '4':	value = '$';	break;
					case '5':	value = '%';	break;
					case '6':	value = '^';	break;
					case '7':	value = '&';	break;
					case '8':	value = '*';	break;
					case '9':	value = '(';	break;
					case '0':	value = ')';	break;
					case '-':	value = '_';	break;
					case '=':	value = '+';	break;
					case '[':	value = '{';	break;
					case ']':	value = '}';	break;
					case '\\':	value = '|';	break;
					case ';':	value = ':';	break;
					case '\'':	value = '\"';	break;
					case ',':	value = '<';	break;
					case '.':	value = '>';	break;
					case '/':	value = '?';	break;
					case '`':	value = '~';	break;
					}
				}
				write(value);
			}
			else
			{
				writeNum(value, 10);
				handled = false;
			}
		}
	}
	if(handled)
	{
		startCursorFlash();	
	}
	return handled;
}

/** writes a base 10 number into the console */
int PixelFontArea::writeNum(int a_num, const int & a_base)
{
	int len = 0;
	const int SIZE = 20;
	char buffer[SIZE];
	int digit;
	do
	{
		++len;
		digit = (a_num % a_base);
		digit += digit<10?'0':('a'-10);
		buffer[SIZE-len] = digit;
		a_num /= a_base;
	}
	while(a_num > 0);
	write(buffer+SIZE-len, len);
	return len;
}

int PixelFontArea::run(Uint32 & a_lastRun, SDL_Surface * a_screen, const SDLKey & a_key)
{
	Uint32 now = SDL_GetTicks();
	Uint32 difference = now - a_lastRun;
	a_lastRun = now;

	update(difference);
	draw(a_screen);

	//Update the screen
	if( a_screen && SDL_Flip( a_screen ) == -1 )
		return false;

	bool running = true;
	SDL_Event e;
	while( SDL_PollEvent( &e ) )
	{
		if(e.type == SDL_KEYDOWN && e.key.keysym.sym == a_key)// press return?
		{
			//Quit the program
			running = false;
		}
		else
		{
			handleSDL(e);
			if(e.type == SDL_KEYDOWN && a_key == SDLK_UNKNOWN) // only need 1 key?
			{
				update(0);
				running = false;
			}
		}
	}
	while(!SDL_PollEvent(0) && SDL_GetTicks() < now+100)
		SDL_Delay( 1 );
	return running;
}

/** 
 * keeps the user in a loop until the given key is pressed, adding key input into the buffer.
 * @param a_screen the SDL_Surface mapped to the screen
 * @param a_key the key that will end the input (pobably SDLK_RETURN). SDLK_UNKNOWN = any key
 * @return the length of the input given by the user.
 */
int PixelFontArea::fillBufferWithInputTill(SDL_Surface * a_screen, const SDLKey & a_key)
{
	int start = getCursorIndex();
	Uint32 time = SDL_GetTicks();
	bool running = true;
	while(this->running() && running && run(time, a_screen, a_key));
	int len = getCursorIndex() - start;
	return len;
}

int PixelFontArea::getch()
{
	return getch(m_screen);
}
int PixelFontArea::getch(SDL_Surface * a_screen)
{
	fillBufferWithInputTill(a_screen, SDLK_UNKNOWN);
	int result = getAt(getCursorIndex() - 1);
	return result;
}

/** 
 * keeps the user in a loop until SDLK_RETURN is pressed
 * @return the value of the positive base 10 integer that was inputted, or -1 if there was an error
 */
int PixelFontArea::readNum()
{
	return readNum(m_screen);
}
/** @param a_screen the SDL_Surface mapped to the screen */
int PixelFontArea::readNum(SDL_Surface * a_screen)
{
	int start = getCursorIndex();
	int len = fillBufferWithInputTill(a_screen, SDLK_RETURN);
	int value = bufferSectionToNumber(start, len);
	return value;
}

/** 
 * keeps the user in a loop until SDLK_RETURN is pressed
 * @param a_buffer where to copy the values written into the console buffer
 * @param a_bufferLen the max number of values to copy
 * @return the length of the input given by the user.
 */
int PixelFontArea::readIntoBuffer(char * a_buffer, const int & a_bufferLen)
{
	return readIntoBuffer(m_screen, a_buffer, a_bufferLen);
}
/** @param a_screen the SDL_Surface mapped to the screen */
int PixelFontArea::readIntoBuffer(SDL_Surface * a_screen, char * a_buffer, const int & a_bufferLen)
{
	int start = getCursorIndex();
	int len = fillBufferWithInputTill(a_screen, SDLK_RETURN);
	for(int i = 0; i < a_bufferLen; ++i)
	{
		a_buffer[i] = getAt(i+start);
	}
	int value = bufferSectionToNumber(start, len);
	return len;
}